/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA.
 **/
package com.genia.toolbox.web.gwt.form.client.visitor.impl; // NOPMD

import java.util.HashMap;
import java.util.Map;

import com.genia.toolbox.web.gwt.basics.client.GwtHelper;
import com.genia.toolbox.web.gwt.basics.client.i18n.GwtI18nMessage;
import com.genia.toolbox.web.gwt.basics.client.widget.HyperLinkButton;
import com.genia.toolbox.web.gwt.basics.client.widget.LazyTabPanel;
import com.genia.toolbox.web.gwt.form.client.CssNames;
import com.genia.toolbox.web.gwt.form.client.form.BasicForm;
import com.genia.toolbox.web.gwt.form.client.form.ButtonItem;
import com.genia.toolbox.web.gwt.form.client.form.CheckBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.DateItem;
import com.genia.toolbox.web.gwt.form.client.form.DateTimeItem;
import com.genia.toolbox.web.gwt.form.client.form.DynamicListBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.DynamicRadioButtonItem;
import com.genia.toolbox.web.gwt.form.client.form.EnumDescriptableValuedItemEntryList;
import com.genia.toolbox.web.gwt.form.client.form.FileUploadItem;
import com.genia.toolbox.web.gwt.form.client.form.Form;
import com.genia.toolbox.web.gwt.form.client.form.HorizontalForm;
import com.genia.toolbox.web.gwt.form.client.form.I18nTextAreaItem;
import com.genia.toolbox.web.gwt.form.client.form.I18nTextBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.ImageItem;
import com.genia.toolbox.web.gwt.form.client.form.InlineFormMultipleItem;
import com.genia.toolbox.web.gwt.form.client.form.InlineFormSimpleItem;
import com.genia.toolbox.web.gwt.form.client.form.LabelItem;
import com.genia.toolbox.web.gwt.form.client.form.LabeledForm;
import com.genia.toolbox.web.gwt.form.client.form.ListBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.MultipleFileUploadItem;
import com.genia.toolbox.web.gwt.form.client.form.PasswordBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.RadioButtonItem;
import com.genia.toolbox.web.gwt.form.client.form.RichTextAreaItem;
import com.genia.toolbox.web.gwt.form.client.form.SimpleValuedItemEntryList;
import com.genia.toolbox.web.gwt.form.client.form.SubFormMultipleItem;
import com.genia.toolbox.web.gwt.form.client.form.SubFormSimpleItem;
import com.genia.toolbox.web.gwt.form.client.form.TabForm;
import com.genia.toolbox.web.gwt.form.client.form.TextAreaItem;
import com.genia.toolbox.web.gwt.form.client.form.TextBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.TimeItem;
import com.genia.toolbox.web.gwt.form.client.form.VerticalForm;
import com.genia.toolbox.web.gwt.form.client.manager.FormManager;
import com.genia.toolbox.web.gwt.form.client.visitor.FormVisitor;
import com.genia.toolbox.web.gwt.form.client.widget.item.ComplexItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.SimpleItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.CheckBoxItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.DateItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.DateTimeItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.DynamicListBoxItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.DynamicRadioButtonItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.FileUploadItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.I18nTextAreaItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.I18nTextBoxItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.InlineFormItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.ListBoxItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.MultipleFileUploadItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.PasswordBoxItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.RadioButtonItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.RichTextAreaItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.SubFormMultipleItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.SubFormSimpleItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.TextAreaItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.TextBoxItemWidget;
import com.genia.toolbox.web.gwt.form.client.widget.item.impl.TimeItemWidget;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * this class generate widget.
 */
public class WidgetGeneratorFormVisitor
    implements FormVisitor
{

  /**
   * the character width of the textArea widget.
   */
  public static final int TEXTAREA_DEFAULT_CHARACTER_WIDTH = 10;

  /**
   * number of visible line in the textArea.
   */
  public static final int TEXTAREA_DEFAULT_VISIBLE_LINE = 5;

  /**
   * the {@link FormManager} that will handle the generated form.
   */
  private transient final FormManager formManager;

  /**
   * a transient Widget object.
   */
  private transient HasWidgets upPanel;

  /**
   * the
   * {@link com.genia.toolbox.web.gwt.form.client.widget.item.SimpleItemWidget}s
   * generated by this visitor.
   */
  private final transient Map<String, SimpleItemWidget> simpleItemWidgets = new HashMap<String, SimpleItemWidget>();

  /**
   * the
   * {@link com.genia.toolbox.web.gwt.form.client.widget.item.ComplexItemWidget}
   * s generated by this visitor.
   */
  private final transient Map<String, ComplexItemWidget> complexItemWidgets = new HashMap<String, ComplexItemWidget>();



  /**
   * constructor.
   * 
   * @param formManager
   *          the {@link FormManager} that will handle this form
   * @param upPanel
   *          the panel to fill with the generated widgets
   */
  public WidgetGeneratorFormVisitor(final FormManager formManager, final HasWidgets upPanel)
  {
    this.formManager = formManager;
    this.upPanel = upPanel;
  }



  /**
   * getter for the complexItemWidgets property.
   * 
   * @return the complexItemWidgets
   */
  public Map<String, ComplexItemWidget> getComplexItemWidgets()
  {
    return complexItemWidgets;
  }



  /**
   * getter for the simpleItemWidgets property.
   * 
   * @return the simpleItemWidgets
   */
  public Map<String, SimpleItemWidget> getSimpleItemWidgets()
  {
    return simpleItemWidgets;
  }



  /**
   * generate checkBoxItem from a CheckBox widget.
   * 
   * @param checkBoxItem
   *          an object CheckBoxItem.
   */
  public void visitCheckBoxItem(final CheckBoxItem checkBoxItem)
  {
    addSimpleItemWidget(new CheckBoxItemWidget(checkBoxItem));
  }



  /**
   * generate dateItem from a textbox widget.
   * 
   * @param dateItem
   *          is of type {@link DateItem}.
   */
  public void visitDateItem(DateItem dateItem)
  {
    addSimpleItemWidget(new DateItemWidget(dateItem));
  }



  /**
   * generate timeItem from a textbox widget.
   * 
   * @param timeItem
   *          is of type {@link TimeItem}.
   */
  public void visitTimeItem(TimeItem timeItem)
  {
    addSimpleItemWidget(new TimeItemWidget(timeItem));
  }



  /**
   * generate dateTimeItem from a textbox widget.
   * 
   * @param dateTimeItem
   *          is of type {@link DateTimeItem}.
   */
  public void visitDateTimeItem(DateTimeItem dateTimeItem)
  {
    addSimpleItemWidget(new DateTimeItemWidget(dateTimeItem));
  }



  /**
   * visit a {@link EnumDescriptableValuedItemEntryList}.
   * 
   * @param valuedItemEntryList
   *          the {@link EnumDescriptableValuedItemEntryList} to visit
   */
  public void visitEnumDescriptableValuedItemEntryList(EnumDescriptableValuedItemEntryList valuedItemEntryList)
  {
    // Do nothing: should not happen.
  }



  /**
   * visit {@link FileUploadItem}.
   * 
   * @param item
   *          the item to visit
   * @see com.genia.toolbox.web.gwt.form.client.visitor.FormVisitor#visitFileUploadItem(com.genia.toolbox.web.gwt.form.client.form.impl.FileUploadItemImpl)
   */
  public void visitFileUploadItem(FileUploadItem item)
  {
    addSimpleItemWidget(new FileUploadItemWidget(item));
  }



  /**
   * create a new widget from a groupForm.
   * 
   * @param horizontalForm
   *          is of type GroupForm. contains a list of Forms.
   */

  public void visitHorizontalForm(final HorizontalForm horizontalForm)
  {
    fillPanel(new HorizontalPanel(), horizontalForm, CssNames.HORIZONTAL_FORM_CSS_STYLE);
  }



  /**
   * this method implement ImageItem from widget Image'gwt.
   * 
   * @param imageItem
   *          is of type ImageItem.
   */
  public void visitImageItem(final ImageItem imageItem)
  {
    final HasWidgets upPanel = this.upPanel;
    // get properties from an ImageItem.
    final String url = GwtHelper.getCorrectUlr(imageItem.getUrl());

    // get the widgets.
    final Image imageWidget = new Image(url);
    final HorizontalPanel hPanel = new HorizontalPanel();

    hPanel.addStyleName(CssNames.IMAGE_CSS_STYLE);
    // Add the widgets to the panel.
    hPanel.add(imageWidget);

    upPanel.add(hPanel);
  }



  /**
   * visit a {@link InlineFormMultipleItem}.
   * 
   * @param inlineFormMultipleItem
   *          the {@link InlineFormMultipleItem} to visit
   */
  public void visitInlineFormMultipleItem(InlineFormMultipleItem inlineFormMultipleItem)
  {
    addComplexItemWidget(new InlineFormItemWidget(formManager, inlineFormMultipleItem));
  }



  /**
   * visit a {@link InlineFormSimpleItem}.
   * 
   * @param inlineFormSimpleItem
   *          the {@link InlineFormSimpleItem} to visit
   */
  public void visitInlineFormSimpleItem(InlineFormSimpleItem inlineFormSimpleItem)
  {
    addComplexItemWidget(new InlineFormItemWidget(formManager, inlineFormSimpleItem));
  }



  /**
   * visit a {@link LabeledForm}.
   * 
   * @param labeledForm
   *          the {@link LabeledForm} to visit
   */
  public void visitLabeledForm(LabeledForm labeledForm)
  {
    labeledForm.getSubForm().accept(this);
  }



  /**
   * this method implement ImageItem from widget Image'gwt.
   * 
   * @param labelItem
   *          is of type LabelItem.
   */
  public void visitLabelItem(final LabelItem labelItem)
  {
    final GwtI18nMessage labelKey = labelItem.getLabelKey();

    final Panel panel = new FlowPanel();
    final Label labelWidget = new Label();

    GwtHelper.fillText(labelWidget, labelKey);

    panel.addStyleName(CssNames.LABEL_PANEL_CSS_STYLE);
    if (labelItem.getName() != null) {
      panel.addStyleName(labelItem.getName());
    }
    panel.add(labelWidget);

    this.upPanel.add(panel);
  }



  /**
   * visit {@link ListBoxItem}.
   * 
   * @param listBoxItem
   *          the item to visit
   * @see FormVisitor#visitListBoxItem(ListBoxItem)
   */
  public void visitListBoxItem(final ListBoxItem listBoxItem)
  {
    addSimpleItemWidget(new ListBoxItemWidget(listBoxItem));
  }



  /**
   * visit {@link MultipleFileUploadItem}.
   * 
   * @param item
   *          the item to visit
   * @see com.genia.toolbox.web.gwt.form.client.visitor.FormVisitor#visitFileUploadItem(com.genia.toolbox.web.gwt.form.client.form.impl.FileUploadItemImpl)
   */
  public void visitMultipleFileUploadItem(MultipleFileUploadItem item)
  {
    addSimpleItemWidget(new MultipleFileUploadItemWidget(item));
  }



  /**
   * Generate a PasswordBoxItem from a PasswordBox widget.
   * 
   * @param passwordBoxItem
   *          is of type passwordBox.
   */
  public void visitPasswordBoxItem(final PasswordBoxItem passwordBoxItem)
  {
    addSimpleItemWidget(new PasswordBoxItemWidget(passwordBoxItem));
  }



  /**
   * Generate RadioButtonItem from RadioButton widget.
   * 
   * @param radioButtonItem
   *          is of type RadioButton.
   */
  public void visitRadioButtonItem(final RadioButtonItem radioButtonItem)
  {
    addSimpleItemWidget(new RadioButtonItemWidget(radioButtonItem));
  }



  /**
   * this method implement a RichTextAreaItem with RichTextArea from gwt.
   * 
   * @param richTextAreaItem
   *          an item.
   */
  public void visitRichTextAreaItem(final RichTextAreaItem richTextAreaItem)
  {
    addSimpleItemWidget(new RichTextAreaItemWidget(richTextAreaItem));
  }



  /**
   * visit a {@link SimpleValuedItemEntryList}.
   * 
   * @param valuedItemEntryList
   *          the {@link SimpleValuedItemEntryList} to visit
   */
  public void visitSimpleValuedItemEntryList(SimpleValuedItemEntryList valuedItemEntryList)
  {
    // Do nothing: should not happen.
  }



  /**
   * visit {@link SubFormMultipleItem}.
   * 
   * @param item
   *          the item to visit
   */
  public void visitSubFormMultipleItem(SubFormMultipleItem item)
  {
    addSimpleItemWidget(new SubFormMultipleItemWidget(formManager, item));
  }



  /**
   * visit {@link SubFormSimpleItem}.
   * 
   * @param item
   *          the item to visit
   */
  public void visitSubFormSimpleItem(final SubFormSimpleItem item)
  {
    addSimpleItemWidget(new SubFormSimpleItemWidget(formManager, item));
  }



  /**
   * create a form from tabForm.
   * 
   * @param tabForm
   *          is of type TabForm.He is composed of Forms.
   */
  public void visitTabForm(final TabForm tabForm)
  {
    final HasWidgets upPanel = this.upPanel;
    final LazyTabPanel tabPanel = new LazyTabPanel();
    tabPanel.addStyleName(CssNames.TAB_PANEL_CSS_STYLE);
    if (tabForm.getName() != null) {
      tabPanel.addStyleName(tabForm.getName());
    }
    upPanel.add(tabPanel);

    for (final LabeledForm labeledForm : tabForm.getForms()) {
      final Panel flowPanel = new HorizontalPanel();
      final Label tabLabel = new Label();
      if (labeledForm.getName() != null) {
        flowPanel.addStyleName(labeledForm.getName());
        tabLabel.addStyleName(labeledForm.getName());
      }
      GwtHelper.fillText(tabLabel, labeledForm.getLabel());
      this.upPanel = flowPanel;
      labeledForm.accept(this);
      tabPanel.add(flowPanel, tabLabel);
    }
    tabPanel.selectTab(0);
    this.upPanel = upPanel;
  }



  /**
   * Generate a TextAreaItem from a TextArea widget.
   * 
   * @param textAreaItem
   *          is of type TextArea.
   */
  public void visitTextAreaItem(final TextAreaItem textAreaItem)
  {
    addSimpleItemWidget(new TextAreaItemWidget(textAreaItem));
  }



  /**
   * generate textBoxItem from a textbox widget.
   * 
   * @param textBoxItem
   *          is of type TextBoxItem.
   */
  public void visitTextBoxItem(final TextBoxItem textBoxItem)
  {
    addSimpleItemWidget(new TextBoxItemWidget(textBoxItem));
  }



  /**
   * add a new {@link SimpleItemWidget} (that must be a {@link Widget}) to the
   * {@link FormManager}.
   * 
   * @param simpleItemWidget
   *          the {@link SimpleItemWidget} to add.
   */
  private void addSimpleItemWidget(SimpleItemWidget simpleItemWidget)
  {
    simpleItemWidgets.put(simpleItemWidget.getItem().getName(), simpleItemWidget);
    simpleItemWidget.addModifyListener(formManager);
    upPanel.add((Widget) simpleItemWidget);
  }



  /**
   * add a new {@link ComplexItemWidget} (that must be a {@link Widget}) to the
   * {@link FormManager}.
   * 
   * @param complexItemWidget
   *          the {@link ComplexItemWidget} to add.
   */
  private void addComplexItemWidget(ComplexItemWidget complexItemWidget)
  {
    complexItemWidgets.put(complexItemWidget.getItem().getName(), complexItemWidget);
    complexItemWidget.addModifyListener(formManager);
    upPanel.add((Widget) complexItemWidget);
  }



  /**
   * visit a {@link VerticalForm}.
   * 
   * @param verticalForm
   *          is of type VerticalForm.
   */
  public void visitVerticalForm(final VerticalForm verticalForm)
  {
    fillPanel(new VerticalPanel(), verticalForm, CssNames.VERTICAL_FORM_CSS_STYLE);
  }



  /**
   * fill the basicForm with the content of panel.
   * 
   * @param panel
   *          from gwt.
   * @param basicForm
   *          is of type BasicForm.
   * @param styleName
   *          the name of the style of the generated pabel
   */
  private void fillPanel(final Panel panel, final BasicForm<? extends Form> basicForm, final String styleName)
  {
    final HasWidgets upPanel = this.upPanel;
    if (styleName != null) {
      panel.addStyleName(styleName);
    }
    if (basicForm.getName() != null) {
      panel.addStyleName(basicForm.getName());
    }
    upPanel.add(panel);
    final HasWidgets oldWidget = this.upPanel;
    this.upPanel = panel;
    for (Form subForm : basicForm.getForms()) {
      subForm.accept(this);
    }
    this.upPanel = oldWidget;
  }



  /**
   * Generate a {@link I18nTextAreaItem} from a I18nTextArea widget.
   * 
   * @param item
   *          is of type I18nTextArea.
   */
  public void visitI18nTextAreaItem(I18nTextAreaItem item)
  {
    addSimpleItemWidget(new I18nTextAreaItemWidget(item));
  }



  /**
   * Generate a {@link I18nTextBoxItem} from a I18nTextArea widget.
   * 
   * @param item
   *          is of type I18nTextBox.
   */
  public void visitI18nTextBoxItem(I18nTextBoxItem item)
  {
    addSimpleItemWidget(new I18nTextBoxItemWidget(item));
  }



  /**
   * visit a {@link DynamicRadioButtonItem}.
   * 
   * @param item
   *          the {@link DynamicRadioButtonItem} to visit
   */
  public void visitDynamicRadioButtonItem(DynamicRadioButtonItem item)
  {
    addSimpleItemWidget(new DynamicRadioButtonItemWidget(formManager.getFormIdentifier(), formManager.getDataIdentifier(), item));
  }



  /**
   * visit a {@link DynamicListBoxItem}.
   * 
   * @param item
   *          the {@link DynamicListBoxItem} to visit
   */
  public void visitDynamicListBoxItem(DynamicListBoxItem item)
  {
    addSimpleItemWidget(new DynamicListBoxItemWidget(formManager.getFormIdentifier(), formManager.getDataIdentifier(), item));
  }

  /**
   * an {@link HyperLinkButton} that will be enabled and disabled with the other
   * control buttons.
   */
  private class ControllerAwareHyperLinkButton
      extends HyperLinkButton
  {

    /**
     * This method is called when a widget is attached to the browser's
     * document. To receive notification after a Widget has been added to the
     * document, override the {@link #onLoad} method.
     * 
     * @see com.google.gwt.user.client.ui.Widget#onAttach()
     */
    @Override
    protected void onAttach()
    {
      try {
        formManager.getFormController().addButton(this);
      }
      finally {
        super.onAttach();
      }
    }



    /**
     * This method is called when a widget is detached from the browser's
     * document. To receive notification before a Widget is removed from the
     * document, override the {@link #onUnload} method.
     * 
     * @see com.google.gwt.user.client.ui.Widget#onDetach()
     */
    @Override
    protected void onDetach()
    {
      try {
        formManager.getFormController().removeButton(this);
      }
      finally {
        super.onDetach();
      }
    }

  }



  /**
   * visit a {@link ButtonItem}.
   * 
   * @param buttonItem
   *          the {@link ButtonItem} to visit
   */
  public void visitButtonItem(final ButtonItem buttonItem)
  {
    final GwtI18nMessage labelKey = buttonItem.getLabelKey();

    final HyperLinkButton hyperLinkButton;

    if (buttonItem.isEnabled()) {
      hyperLinkButton = new ControllerAwareHyperLinkButton();
    }
    else {
      hyperLinkButton = new HyperLinkButton();
      hyperLinkButton.setEnabled(false);
    }
    if (labelKey != null) {
      GwtHelper.fillText(hyperLinkButton, labelKey);
      GwtHelper.fillTitle(hyperLinkButton, labelKey);
    }
    hyperLinkButton.addStyleName(CssNames.BUTTON_PANEL_CSS_STYLE);
    if (buttonItem.getName() != null) {
      hyperLinkButton.addStyleName(buttonItem.getName());
    }

    final boolean updateForm;
    final ClickListener clickListener;
    if (ButtonItem.BACK_ACTION.equals(buttonItem.getAction())) {
      updateForm = false;
      clickListener = new ClickListener()
      {
        public void onClick(Widget sender)
        {
          formManager.getFormController().goBack();
        }
      };
    }

    else if (ButtonItem.RESET_ACTION.equals(buttonItem.getAction())) {
      updateForm = true;
      clickListener = new ClickListener()
      {
        public void onClick(Widget sender)
        {
          formManager.getFormController().resetForm();
        }
      };
    }

    else if (ButtonItem.URL_ACTION.equals(buttonItem.getAction())) {
      updateForm = false;
      clickListener = new ClickListener()
      {
        public void onClick(Widget sender)
        {
          GwtHelper.redirect(GwtHelper.getCorrectUlr(buttonItem.getUrl()));
        }
      };
    }
    else {
      updateForm = true;
      clickListener = new ClickListener()
      {
        public void onClick(Widget sender)
        {
          // Do nothing.
        }
      };
    }

    if (buttonItem.isSave()) {
      hyperLinkButton.addClickListener(new ClickListener()
      {

        public void onClick(final Widget sender)
        {
          formManager.getFormController().submitForm(new Command()
          {

            public void execute()
            {
              clickListener.onClick(sender);
            }

          }, updateForm);
        }

      });
    }
    else {
      hyperLinkButton.addClickListener(clickListener);
    }

    this.upPanel.add(hyperLinkButton);
  }

}
